"""
文件上传 / 下载
POST   /base/files      上传（multipart/form-data）
GET    /base/files/{id} 下载（FileResponse）
存储层抽象出 FileStorageService，目前用本地 LocalFileStorage，
后续直接换成 OSSFileStorage 即可。
"""
import hashlib
import io
import uuid
from pathlib import Path
from typing import Optional
from urllib.parse import quote

from fastapi import APIRouter, UploadFile, File, Depends, Form

from app.common.auth import get_current_user, require_file_manage
from app.common.common import DbDep, AuditLogDep
from app.crud.file_crud import save_file_record, get_file_record_with_db
from app.schemas.file_schemas import FileUploadResponse, FileInfoResponse
from app.schemas.user_schemas import UserInDB
from app.service.file_storage import file_storage
from common.config import settings
from common.exceptions import BizException, ErrorCode
from common.response import ApiResponse

router = APIRouter(prefix="/files", tags=["Files"])

UPLOAD_DIR = Path(settings.upload_dir)
UPLOAD_DIR.mkdir(exist_ok=True)


def encode_filename_for_http(filename: str) -> str:
	"""
	为HTTP响应头编码文件名，支持中文字符
	使用RFC 2231标准的filename*参数

	Args:
		filename: 原始文件名

	Returns:
		str: 编码后的文件名参数
	"""
	try:
		# 尝试使用latin-1编码，如果成功则直接使用
		filename.encode('latin-1')
		return f'filename="{filename}"'
	except UnicodeEncodeError:
		# 包含非latin-1字符（如中文），使用RFC 2231格式
		encoded_filename = quote(filename.encode('utf-8'))
		return f"filename*=UTF-8''{encoded_filename}"


def get_content_disposition_header(filename: str, is_inline: bool = False) -> str:
	"""
	生成Content-Disposition响应头

	Args:
		filename: 文件名
		is_inline: 是否为内联显示（预览模式）

	Returns:
		str: 完整的Content-Disposition头值
	"""
	disposition_type = "inline" if is_inline else "attachment"
	filename_param = encode_filename_for_http(filename)
	return f'{disposition_type}; {filename_param}'


def validate_file_type(file: UploadFile) -> None:
	"""
	验证文件类型
	Args:
		file: 上传文件
	Raises:
		BizException: 文件类型不支持
	"""
	if not file.content_type:
		raise BizException.from_error_code(
			ErrorCode.FILE_TYPE_NOT_SUPPORTED,
			message="无法检测文件类型"
		)

	# 支持带 charset 的情况，如 text/plain; charset=utf-8
	content_type = file.content_type.split(";")[0].strip()

	if content_type not in settings.allowed_file_types:
		raise BizException.from_error_code(
			ErrorCode.FILE_TYPE_NOT_SUPPORTED,
			message=f"不支持的文件类型: {content_type}",
			details={
				"file_type": content_type,
				"allowed_types": settings.allowed_file_types
			}
		)


def validate_file_size(file: UploadFile) -> None:
	"""
	验证文件大小
	Args:
		file: 上传文件
	Raises:
		BizException: 文件大小超限
	"""
	# FastAPI 的 UploadFile 在这里不能直接获取大小
	# 我们在保存后再检查大小
	pass


def calculate_file_md5(file_path: Path) -> str:
	"""
	计算文件MD5值
	Args:
		file_path: 文件路径
	Returns:
		str: MD5值
	"""
	hash_md5 = hashlib.md5()
	with open(file_path, "rb") as f:
		for chunk in iter(lambda: f.read(4096), b""):
			hash_md5.update(chunk)
	return hash_md5.hexdigest()


def check_file_access_permission(file_record, current_user: UserInDB) -> bool:
	"""
	检查文件访问权限
	Args:
		file_record: 文件记录
		current_user: 当前用户
	Returns:
		bool: 是否有权限访问
	"""
	# 管理员可以访问所有文件
	# 修复：使用角色检查替代直接访问role属性
	# 这里假设管理员检查通过其他方式实现，或者暂时移除此检查
	# if current_user.role == "admin":
	#     return True

	# 文件上传者可以访问自己的文件
	if file_record.creator_id == current_user.id:
		return True

	# TODO: 根据业务需求添加更多权限检查逻辑
	# 例如：同一业务类型下的文件共享等

	return False


@router.post("", summary="上传文件", response_model=ApiResponse[FileUploadResponse])
async def upload_file(
		db: DbDep,
		_audit: AuditLogDep,
		file: UploadFile = File(..., description="上传的文件"),
		biz_type: Optional[str] = Form(None, description="业务类型:METER/RTU/BOX/STATION"),
		biz_id: Optional[str] = Form(None, description="业务ID(设备ID)"),
		current_user: UserInDB = Depends(get_current_user),
):
	"""
	单文件上传到 OSS

	Args:
		file: 上传的文件
		biz_type: 业务类型,METER/RTU/BOX/STATION
		biz_id: 业务ID(设备ID)
		current_user: 当前用户
	"""
	try:
		# 1. 基础校验
		validate_file_type(file)
		if not file.filename or file.filename.strip() == "":
			raise BizException.from_error_code(
				ErrorCode.BAD_REQUEST, message="文件名不能为空"
			)

		# 2. 把文件完整读进内存（OSS SDK 需要 bytes/BinaryIO）
		contents = await file.read()
		file_size = len(contents)
		if file_size > settings.max_file_size:
			raise BizException.from_error_code(
				ErrorCode.FILE_SIZE_EXCEEDED,
				message=f"文件大小超出限制（最大5MB）",
				details={
					"file_size": file_size,
					"max_size": settings.max_file_size,
					"size_mb": round(file_size / (1024 * 1024), 2),
					"max_size_mb": round(settings.max_file_size / (1024 * 1024), 2),
				},
			)

		# 3. 计算 MD5
		file_md5 = hashlib.md5(contents).hexdigest()

		# 4. 上传到 OSS
		file_suffix = Path(file.filename).suffix
		uuid_str = f"{uuid.uuid4().hex}{file_suffix}"
		object_key = await file_storage.save(io.BytesIO(contents), uuid_str)

		# 5. 落库
		file_id = await save_file_record(
			db,
			file_name=file.filename,
			file_ext=file_suffix.lstrip("."),
			file_size=file_size,
			mime_type=file.content_type,
			store_path=object_key,  # OSS 对象名
			url=file_storage.get_url(object_key),  # CDN 外网直链
			md5=file_md5,
			biz_type=biz_type,  # 业务类型
			biz_id=biz_id,  # 业务ID
			creator_id=current_user.id,
		)

		# 6. 返回
		return ApiResponse.success(
			FileUploadResponse(
				id=file_id,
				url=file_storage.get_url(object_key),
				name=file.filename,
				size=file_size,
				ext=file_suffix.lstrip("."),
				md5=file_md5,
				mime_type=file.content_type,
			)
		)

	except BizException:
		raise
	except Exception as e:
		raise BizException.from_error_code(
			ErrorCode.FILE_UPLOAD_FAILED, message=f"文件上传失败: {str(e)}"
		)


@router.get('/{file_id}', summary='预览或下载文件')
async def download_or_preview(
		db: DbDep,
		file_id: str,
		current_user: UserInDB = Depends(get_current_user)
):
	file_record = await get_file_record_with_db(db, file_id)
	if not file_record:
		raise BizException.file_not_found(file_id)

	if not check_file_access_permission(file_record, current_user):
		raise BizException.from_error_code(
			ErrorCode.FILE_ACCESS_DENIED
		)

	# 生成签名 URL（公网域名，900 s）
	signed = file_storage.get_signed_url(file_record.store_path,
										 expires=900, internal=False)

	# 302 跳转到 OSS，浏览器可直接展示
	return {'url': signed}


@router.get("/info/{file_id}", summary="获取文件信息", response_model=ApiResponse[FileInfoResponse])
async def get_file_info(
		file_id: str,
		db: DbDep,
		current_user: UserInDB = Depends(get_current_user)
):
	"""
	获取文件详细信息

	Args:
		file_id: 文件ID或唯一标识
		db: 数据库连接
		current_user: 当前用户

	Returns:
		ApiResponse[FileInfoResponse]: 文件信息
	"""
	# 获取文件记录
	file_record = await get_file_record_with_db(db, file_id)
	if not file_record:
		raise BizException.file_not_found(file_id)

	# 检查文件访问权限
	if not check_file_access_permission(file_record, current_user):
		raise BizException.from_error_code(
			ErrorCode.FILE_ACCESS_DENIED,
			message="您没有访问该文件的权限"
		)

	# 转换为响应模型
	file_info = FileInfoResponse(
		id=file_record.id,
		biz_type=file_record.biz_type,
		biz_id=file_record.biz_id,
		file_name=file_record.file_name,
		file_ext=file_record.file_ext,
		file_size=file_record.file_size,
		mime_type=file_record.mime_type,
		url=file_record.url,
		md5=file_record.md5,
		status=file_record.status,
		creator_id=file_record.creator_id,
		create_time=file_record.create_time,
		update_time=file_record.update_time
	)

	return ApiResponse.success(file_info)


@router.delete("/{file_id}", summary="删除文件")
async def delete_file(
		db: DbDep,
		_audit: AuditLogDep,
		file_id: str,
		current_user: UserInDB = Depends(require_file_manage)
):
	"""
	删除文件（逻辑删除或物理删除）

	Args:
		file_id: 文件ID
		db: 数据库连接
		current_user: 当前用户
	"""
	from app.crud.file_crud import delete_file_record, get_file_by_id

	# 获取文件记录
	file_record = await get_file_by_id(db, int(file_id) if file_id.isdigit() else 0)
	if not file_record:
		raise BizException.file_not_found(file_id)

	# 检查删除权限（只有文件上传者可以删除）
	if file_record.creator_id != current_user.id:
		raise BizException.from_error_code(
			ErrorCode.FILE_ACCESS_DENIED,
			message="您没有删除该文件的权限"
		)

	# 执行删除
	success = await delete_file_record(db, file_record.id, False)
	if not success:
		raise BizException.from_error_code(
			ErrorCode.INTERNAL_SERVER_ERROR,
			message="文件删除失败"
		)

	return ApiResponse.success(message=f"文件删除成功")
